package jvm.jtacck;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.logging.FileHandler;
import java.util.logging.Filter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import java.util.logging.StreamHandler;

import jvm.jtacck.logging.LoggingOutputStream;
import jvm.jtacck.logging.StdOutErrLevel;
import jvm.jtacck.test.db.ScriptRunner;

/**
 * TODO Doc
 */
/**
 * @author Cody Schroeder
 *
 */
public final class Util {
    
    /** A utility logger meant for system-wide use. */
    public static final Logger logger = Logger.getLogger("Global");
    /** The value of System.out on this class' static instantiation. */
    public static final PrintStream stdout = System.out;
    /** The value of System.err on this class' static instantiation. */
    public static final PrintStream stderr = System.err;
    
    public static String sqlPath;
    public static ScriptRunner sr;
    
    static {
        // Set logging to use a file as well.
        try {
            FileHandler fileHandler = new FileHandler("jtacck-server%u.log", true);
            fileHandler.setFormatter(new SimpleFormatter());
            logger.addHandler(fileHandler);
        } catch (Exception e) {
            logger.severe("FileHandler for Logger could not be attached.");
        }
    }
    
    /**
     * Replaces System.out and System.err with streams that redirect to both
     * {@link logger} and their respective streams with the {@link Level}s
     * {@link StdOutErrLevel#STDOUT} and {@link StdOutErrLevel#STDERR},
     * respectively.
     */
    public static void logStdOutErr() {
        // Remove global handlers that deal with streams (System.out/err).
        Logger globalLogger = Logger.getAnonymousLogger().getParent();
        for (Handler h : globalLogger.getHandlers())
            if (h instanceof StreamHandler)
                globalLogger.removeHandler(h);
        
        // Force stdout/stderr into the logger.
        OutputStream los = new LoggingOutputStream(logger, StdOutErrLevel.STDOUT);
        System.setOut(new PrintStream(los, true));
        
        los = new LoggingOutputStream(logger, StdOutErrLevel.STDERR);
        System.setErr(new PrintStream(los, true));
        
        // A simple Filter that only allows an exact Level.
        final class LevelFilter implements Filter {
            final Level level;
            public LevelFilter(Level level) {
                this.level = level;
            }
            public final boolean isLoggable(LogRecord record) {
                return record.getLevel() == level;
            }
        }
        
        // Add handlers to logger that will push STDOUT/STDERR levels to their appropriate streams.
        StreamHandler stdoutHandler = new StreamHandler(stdout, new SimpleFormatter());
        stdoutHandler.setLevel(Level.ALL);
        stdoutHandler.setFilter(new LevelFilter(StdOutErrLevel.STDOUT));
        logger.addHandler(stdoutHandler);
        
        StreamHandler stderrHandler = new StreamHandler(stderr, new SimpleFormatter());
        stderrHandler.setLevel(Level.ALL);
        stderrHandler.setFilter(new LevelFilter(StdOutErrLevel.STDERR));
        logger.addHandler(stderrHandler);
    }

    /**
     * Restores System.out and System.err to their values on this class' static
     * initialization. This can be used to undo {@link Util#logStdOutErr()}.
     */
    public static void restoreStdOutErr() {
        System.setOut(stdout);
        System.setErr(stderr);
    }

    /**
     * @return <code>false</code> iff any Object passed is null
     */
    public static boolean noNulls (Object... os) {
    	for (int i = 0; i < os.length; i++) {
    	    if (os[i] == null) {
    	        return false;
    	    }
    	}
	
    	return true;
    }
    
    /**
     * Creates a script runner to run SQL scripts
     * 
     * @param user
     *          The user of the database
     * @param password
     *          The password for the user
     * @param server
     *          The server name of the database
     * @param port
     *          The port the database is located at
     * @param name
     *          The name of the database
     */
    private static void makeScriptRunner() {
        
        String username = System.getProperty("test.dbuser");
        String password = System.getProperty("test.dbpass");
        String server = System.getProperty("test.dbserver");
        String port = System.getProperty("test.dbport");
        String dbname = System.getProperty("test.dbname");

        String sqlPath = System.getProperty("test.dbsqlpath");

        
        Util.sqlPath = sqlPath;
        String dbAddress = server+":"+port+"/"+dbname;
        sr = null;
        try {
            Class.forName("org.postgresql.Driver");
            Connection dbConn = DriverManager.getConnection("jdbc:postgresql://"
                    + dbAddress, username, password);
            sr = new ScriptRunner(dbConn, false, true);
            sr.setLogWriter(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * Runs the given sql script in the database. It assumes the file is located
     * with the path given by the System Property "test.dbsqlpath".
     * 
     * @param sr
     *              The ScriptRunner to run the script with
     * @param filename 
     *              the name of the script to run.
     * 
     * @throws SQLException 
     * @throws IOException 
     * @throws FileNotFoundException 
     */
    public static void runScript(String filename) 
            throws FileNotFoundException, IOException, SQLException {
        if (sr == null) {
            makeScriptRunner();
        }
        sr.runScript(new BufferedReader(new FileReader(sqlPath + 
                File.separator +  filename)));
    }
    
    /**
     * Runs the setup scripts for the web site test suite.
     * @throws FileNotFoundException
     * @throws IOException
     * @throws SQLException
     */
    public static void setUp() 
        throws FileNotFoundException, IOException, SQLException {
        setUp("small-test.sql");
    }
    /**
     * Runs the generic setup script and a customized setup script given by
     * filename
     * @param filename
     *              The name of the script to run. Must be in the 'sqlpath' 
     *              directory
     * @throws FileNotFoundException
     * @throws IOException
     * @throws SQLException
     */
    public static void setUp(String filename)
        throws FileNotFoundException, IOException, SQLException {
        runScript("setup.sql");
        runScript(filename);
    }
    
    /**
     * Runs the teardown.sql that removes all the tables from the database. This
     * allows for a fresh database to be created for the next test.
     * @throws SQLException 
     * @throws IOException 
     * @throws FileNotFoundException 
     */
    public static void tearDown() 
        throws FileNotFoundException, IOException, SQLException {
        runScript("teardown.sql");
    }
}